"""Broadcast service module used to send broadcast msgs and set a recv server

"""

import socket
from queue import Queue
from threading import Thread
import ma.log
import ma.const
import ma.net.netutils
import time


class BroadcastPacketSender(object):
    def __init__(self, broadcast_addr="", broadcast_port=0):
        #initialize __log for BroadcastListenServer
        self.__log = ma.log.get_logger("ma.net")
        
        try:
            self.__log.debug('initiating socket for BroadcastPacketSender')
            # make UDP socket
            self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # allow send/recieve from broacast address
            self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            # tell OS to make the port re-usable as soon as possible 
            self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # to not allow packets to be routed through a router to keep to the internal network
            #self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_DONTROUTE, 1)
            # get network parameters for address/port
            self.__broadcast_address = (broadcast_addr, broadcast_port)
            
            self.__log.info('completed init of socket for BroadcastPacketSender at %s', str(self.__broadcast_address))
        except Exception as err_msg:
            self.__log.error("Error while creating broadcast packet sender socket: %s", err_msg)


    @staticmethod
    def get_standard_brdcst_addr():    
        # get network parameters address/port
        broadcast_addr = str.strip(ma.const.XmlData.get_str_data(ma.const.xml_broadcast_address))
        broadcast_port = ma.const.XmlData.get_int_data(ma.const.xml_broadcast_port)
        return broadcast_addr, broadcast_port
        
    
    def send_message(self, buff, recepient=None):
        if recepient == None:
            recepient = self.__broadcast_address
            
        if buff != "":
            self.__log.debug('Sending broadcast packet to %s ', str(recepient))
            
            remaining = len(buff)
            #keep running until the whole packet is through
            while remaining > 0:
                try:
                    #send the remaining amount of bytes
                    x = self.__sock.sendto(buff[len(buff)-remaining:], recepient)
                    
                    self.__log.debug('Sent broadcast packet\'s %d bytes', x)
                    remaining -= x
                except Exception as err_msg:
                    self.__log.error("Error while sending broadcast packet: %s", err_msg)
                
            self.__log.debug('Sent the broadcast packet of %d bytes to %s', len(buff), str(recepient))
        
            
    def __del__(self):
        try:
            """Destructor to close the socket"""
            self.__sock.shutdown(socket.SHUT_RDWR)
        except Exception as err_msg:
            self.__log.error("Error while trying to destruct the BroadcastPacketSender socket: %s", err_msg)



class BroadcastListenServer(Thread):
    """Class used to get broadcast or UDP packets on the network.
    
    Binding to an empty address will bind to all interfaces, recv_callback
    is the function which is called whenever a packet is received. 
    discard_local if true will discard all packets having the current node's
    IP. discard_port if true will discard all the port information after
    receiving packets
    """
    
    def __init__(self, binding_net_addr="", broadcast_port=0, recv_callback=None, discard_local=False, discard_port=False):
        #initialize __log for BroadcastListenServer. Need to give 
        self.__log = ma.log.get_logger("ma.net")
        
        try:
            self.__log.debug('initiating socket for BroadcastListenServer')
            # make UDP socket
            self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            # allow send/recieve from broacast address
            self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            # tell OS to make the port re-usable as soon as possible
            self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            # get network parameters and bind to address/port
            self.__sock.bind((binding_net_addr, broadcast_port))
            
            # get internal ip for recording internal locks
            interface_name = ma.const.XmlData.get_str_data(ma.const.xml_network_interface)
            self.__internal_ip = ma.net.netutils.get_ip_address(interface_name)
            
            # set options
            self.__discard_local_pckts = discard_local
            self.__discard_port = discard_port 
            
            # make a message queue
            self.msg_queue = Queue()
            self.__to_kill = False
            self.__pckt_buff_size = ma.const.XmlData.get_int_data(ma.const.xml_broadcast_buff_size)
            self.__callback_func = recv_callback
            
            self.__log.debug('completed init of socket for BroadcastListenServer at %s', str((binding_net_addr, broadcast_port)))
            
            # initialize the thread
            Thread.__init__(self)
            self.__log.info('Setup server thread for BroadcastListenServer complete')
        except Exception as err_msg:
            self.__log.error("Error while creating broadcast listen socket: %s", err_msg)
    
    
    @staticmethod
    def get_standard_brdcst_bind_addr():    
        # get network parameters address/port
        binding_net_addr = str.strip(ma.const.XmlData.get_str_data(ma.const.xml_binding_address))
        broadcast_port = ma.const.XmlData.get_int_data(ma.const.xml_broadcast_port)
        return binding_net_addr, broadcast_port
    
        
    def run(self):
        self.__log.info('Started BroadcastListenServer thread ... listening for broadcasts')
        
        # loop to listen to broadcast messages
        while not self.__to_kill:
            # wait and receive any broadcast messages 
            # try except to catch broken pipe errors
            try:
                message, address = self.__sock.recvfrom(self.__pckt_buff_size)
            
                # ignore if packet from local machine and option set 
                if self.__discard_local_pckts == True and address[0] == self.__internal_ip:
                    continue
                
                # throw away port info if option set
                if self.__discard_port == True:
                    address = address[0]
                    
                # put it into the message queue
                self.msg_queue.put([address, message])

                self.__log.debug("Got broadcast packet from %s and message is: %s", str(address),str(message))
                
                # call the callback function after receiving packet
                if self.__callback_func != None:
                    self.__callback_func.__call__()
            
            except Exception as err_msg:
                self.__log.error("Error while receiving/waiting for broadcast messages: %s", err_msg)
                
        self.__log.info("Exiting BroadcastListenServer thread")
    
    
    def killServerThread(self):
        """This function is used to kill the running server
        """
        self.__to_kill = True
    
    
    def __del__(self):
        """Destructor to close the socket"""
        try:
            # to kill thread
            self.killServerThread()
            self.__sock.shutdown(socket.SHUT_RDWR)
        except Exception as err_msg:
            self.__log.error("Error while trying to destruct the BroadcastListenServer socket: %s", err_msg)
            
            
if __name__ == '__main__':
    #main for testing
    def f():
        msg = svr.msg_queue.get()
        print("FROM %s: %s" % (str(msg[0]), msg[1]))
    binding_net_addr, broadcast_port = BroadcastListenServer.get_standard_brdcst_bind_addr()
    svr = BroadcastListenServer(binding_net_addr, broadcast_port, f, True, True)
    svr.start()
    svr.join()
